/*
 * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.awt;

/**
 * A set of attributes which control a print job.
 * <p>
 * Instances of this class control the number of copies, default selection,
 * destination, print dialog, file and printer names, page ranges, multiple
 * document handling (including collation), and multi-page imposition (such
 * as duplex) of every print job which uses the instance. Attribute names are
 * compliant with the Internet Printing Protocol (IPP) 1.1 where possible.
 * Attribute values are partially compliant where possible.
 * <p>
 * To use a method which takes an inner class type, pass a reference to
 * one of the constant fields of the inner class. Client code cannot create
 * new instances of the inner class types because none of those classes
 * has a public constructor. For example, to set the print dialog type to
 * the cross-platform, pure Java print dialog, use the following code:
 * <pre>
 * import java.awt.JobAttributes;
 *
 * public class PureJavaPrintDialogExample {
 *     public void setPureJavaPrintDialog(JobAttributes jobAttributes) {
 *         jobAttributes.setDialog(JobAttributes.DialogType.COMMON);
 *     }
 * }
 * </pre>
 * <p>
 * Every IPP attribute which supports an <i>attributeName</i>-default value
 * has a corresponding <code>set<i>attributeName</i>ToDefault</code> method.
 * Default value fields are not provided.
 *
 * @author David Mendenhall
 * @since 1.3
 */
public final class JobAttributes implements Cloneable {

  /**
   * A type-safe enumeration of possible default selection states.
   *
   * @since 1.3
   */
  public static final class DefaultSelectionType extends AttributeValue {

    private static final int I_ALL = 0;
    private static final int I_RANGE = 1;
    private static final int I_SELECTION = 2;

    private static final String NAMES[] = {
        "all", "range", "selection"
    };

    /**
     * The <code>DefaultSelectionType</code> instance to use for
     * specifying that all pages of the job should be printed.
     */
    public static final DefaultSelectionType ALL =
        new DefaultSelectionType(I_ALL);
    /**
     * The <code>DefaultSelectionType</code> instance to use for
     * specifying that a range of pages of the job should be printed.
     */
    public static final DefaultSelectionType RANGE =
        new DefaultSelectionType(I_RANGE);
    /**
     * The <code>DefaultSelectionType</code> instance to use for
     * specifying that the current selection should be printed.
     */
    public static final DefaultSelectionType SELECTION =
        new DefaultSelectionType(I_SELECTION);

    private DefaultSelectionType(int type) {
      super(type, NAMES);
    }
  }

  /**
   * A type-safe enumeration of possible job destinations.
   *
   * @since 1.3
   */
  public static final class DestinationType extends AttributeValue {

    private static final int I_FILE = 0;
    private static final int I_PRINTER = 1;

    private static final String NAMES[] = {
        "file", "printer"
    };

    /**
     * The <code>DestinationType</code> instance to use for
     * specifying print to file.
     */
    public static final DestinationType FILE =
        new DestinationType(I_FILE);
    /**
     * The <code>DestinationType</code> instance to use for
     * specifying print to printer.
     */
    public static final DestinationType PRINTER =
        new DestinationType(I_PRINTER);

    private DestinationType(int type) {
      super(type, NAMES);
    }
  }

  /**
   * A type-safe enumeration of possible dialogs to display to the user.
   *
   * @since 1.3
   */
  public static final class DialogType extends AttributeValue {

    private static final int I_COMMON = 0;
    private static final int I_NATIVE = 1;
    private static final int I_NONE = 2;

    private static final String NAMES[] = {
        "common", "native", "none"
    };

    /**
     * The <code>DialogType</code> instance to use for
     * specifying the cross-platform, pure Java print dialog.
     */
    public static final DialogType COMMON = new DialogType(I_COMMON);
    /**
     * The <code>DialogType</code> instance to use for
     * specifying the platform's native print dialog.
     */
    public static final DialogType NATIVE = new DialogType(I_NATIVE);
    /**
     * The <code>DialogType</code> instance to use for
     * specifying no print dialog.
     */
    public static final DialogType NONE = new DialogType(I_NONE);

    private DialogType(int type) {
      super(type, NAMES);
    }
  }

  /**
   * A type-safe enumeration of possible multiple copy handling states.
   * It is used to control how the sheets of multiple copies of a single
   * document are collated.
   *
   * @since 1.3
   */
  public static final class MultipleDocumentHandlingType extends
      AttributeValue {

    private static final int I_SEPARATE_DOCUMENTS_COLLATED_COPIES = 0;
    private static final int I_SEPARATE_DOCUMENTS_UNCOLLATED_COPIES = 1;

    private static final String NAMES[] = {
        "separate-documents-collated-copies",
        "separate-documents-uncollated-copies"
    };

    /**
     * The <code>MultipleDocumentHandlingType</code> instance to use for specifying
     * that the job should be divided into separate, collated copies.
     */
    public static final MultipleDocumentHandlingType
        SEPARATE_DOCUMENTS_COLLATED_COPIES =
        new MultipleDocumentHandlingType(
            I_SEPARATE_DOCUMENTS_COLLATED_COPIES);
    /**
     * The <code>MultipleDocumentHandlingType</code> instance to use for specifying
     * that the job should be divided into separate, uncollated copies.
     */
    public static final MultipleDocumentHandlingType
        SEPARATE_DOCUMENTS_UNCOLLATED_COPIES =
        new MultipleDocumentHandlingType(
            I_SEPARATE_DOCUMENTS_UNCOLLATED_COPIES);

    private MultipleDocumentHandlingType(int type) {
      super(type, NAMES);
    }
  }

  /**
   * A type-safe enumeration of possible multi-page impositions. These
   * impositions are in compliance with IPP 1.1.
   *
   * @since 1.3
   */
  public static final class SidesType extends AttributeValue {

    private static final int I_ONE_SIDED = 0;
    private static final int I_TWO_SIDED_LONG_EDGE = 1;
    private static final int I_TWO_SIDED_SHORT_EDGE = 2;

    private static final String NAMES[] = {
        "one-sided", "two-sided-long-edge", "two-sided-short-edge"
    };

    /**
     * The <code>SidesType</code> instance to use for specifying that
     * consecutive job pages should be printed upon the same side of
     * consecutive media sheets.
     */
    public static final SidesType ONE_SIDED = new SidesType(I_ONE_SIDED);
    /**
     * The <code>SidesType</code> instance to use for specifying that
     * consecutive job pages should be printed upon front and back sides
     * of consecutive media sheets, such that the orientation of each pair
     * of pages on the medium would be correct for the reader as if for
     * binding on the long edge.
     */
    public static final SidesType TWO_SIDED_LONG_EDGE =
        new SidesType(I_TWO_SIDED_LONG_EDGE);
    /**
     * The <code>SidesType</code> instance to use for specifying that
     * consecutive job pages should be printed upon front and back sides
     * of consecutive media sheets, such that the orientation of each pair
     * of pages on the medium would be correct for the reader as if for
     * binding on the short edge.
     */
    public static final SidesType TWO_SIDED_SHORT_EDGE =
        new SidesType(I_TWO_SIDED_SHORT_EDGE);

    private SidesType(int type) {
      super(type, NAMES);
    }
  }

  private int copies;
  private DefaultSelectionType defaultSelection;
  private DestinationType destination;
  private DialogType dialog;
  private String fileName;
  private int fromPage;
  private int maxPage;
  private int minPage;
  private MultipleDocumentHandlingType multipleDocumentHandling;
  private int[][] pageRanges;
  private int prFirst;
  private int prLast;
  private String printer;
  private SidesType sides;
  private int toPage;

  /**
   * Constructs a <code>JobAttributes</code> instance with default
   * values for every attribute.  The dialog defaults to
   * <code>DialogType.NATIVE</code>.  Min page defaults to
   * <code>1</code>.  Max page defaults to <code>Integer.MAX_VALUE</code>.
   * Destination defaults to <code>DestinationType.PRINTER</code>.
   * Selection defaults to <code>DefaultSelectionType.ALL</code>.
   * Number of copies defaults to <code>1</code>. Multiple document handling defaults
   * to <code>MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES</code>.
   * Sides defaults to <code>SidesType.ONE_SIDED</code>. File name defaults
   * to <code>null</code>.
   */
  public JobAttributes() {
    setCopiesToDefault();
    setDefaultSelection(DefaultSelectionType.ALL);
    setDestination(DestinationType.PRINTER);
    setDialog(DialogType.NATIVE);
    setMaxPage(Integer.MAX_VALUE);
    setMinPage(1);
    setMultipleDocumentHandlingToDefault();
    setSidesToDefault();
  }

  /**
   * Constructs a <code>JobAttributes</code> instance which is a copy
   * of the supplied <code>JobAttributes</code>.
   *
   * @param obj the <code>JobAttributes</code> to copy
   */
  public JobAttributes(JobAttributes obj) {
    set(obj);
  }

  /**
   * Constructs a <code>JobAttributes</code> instance with the
   * specified values for every attribute.
   *
   * @param copies an integer greater than 0
   * @param defaultSelection <code>DefaultSelectionType.ALL</code>, <code>DefaultSelectionType.RANGE</code>,
   * or <code>DefaultSelectionType.SELECTION</code>
   * @param destination <code>DesintationType.FILE</code> or <code>DesintationType.PRINTER</code>
   * @param dialog <code>DialogType.COMMON</code>, <code>DialogType.NATIVE</code>, or
   * <code>DialogType.NONE</code>
   * @param fileName the possibly <code>null</code> file name
   * @param maxPage an integer greater than zero and greater than or equal to <i>minPage</i>
   * @param minPage an integer greater than zero and less than or equal to <i>maxPage</i>
   * @param multipleDocumentHandling <code>MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES</code>
   * or <code>MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES</code>
   * @param pageRanges an array of integer arrays of two elements; an array is interpreted as a
   * range spanning all pages including and between the specified pages; ranges must be in ascending
   * order and must not overlap; specified page numbers cannot be less than <i>minPage</i> nor
   * greater than <i>maxPage</i>; for example:
   * <pre>
   *          (new int[][] { new int[] { 1, 3 }, new int[] { 5, 5 },
   *                         new int[] { 15, 19 } }),
   *          </pre>
   * specifies pages 1, 2, 3, 5, 15, 16, 17, 18, and 19. Note that (<code>new int[][] { new int[] {
   * 1, 1 }, new int[] { 1, 2 } }</code>), is an invalid set of page ranges because the two ranges
   * overlap
   * @param printer the possibly <code>null</code> printer name
   * @param sides <code>SidesType.ONE_SIDED</code>, <code>SidesType.TWO_SIDED_LONG_EDGE</code>, or
   * <code>SidesType.TWO_SIDED_SHORT_EDGE</code>
   * @throws IllegalArgumentException if one or more of the above conditions is violated
   */
  public JobAttributes(int copies, DefaultSelectionType defaultSelection,
      DestinationType destination, DialogType dialog,
      String fileName, int maxPage, int minPage,
      MultipleDocumentHandlingType multipleDocumentHandling,
      int[][] pageRanges, String printer, SidesType sides) {
    setCopies(copies);
    setDefaultSelection(defaultSelection);
    setDestination(destination);
    setDialog(dialog);
    setFileName(fileName);
    setMaxPage(maxPage);
    setMinPage(minPage);
    setMultipleDocumentHandling(multipleDocumentHandling);
    setPageRanges(pageRanges);
    setPrinter(printer);
    setSides(sides);
  }

  /**
   * Creates and returns a copy of this <code>JobAttributes</code>.
   *
   * @return the newly created copy; it is safe to cast this Object into a
   * <code>JobAttributes</code>
   */
  public Object clone() {
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      // Since we implement Cloneable, this should never happen
      throw new InternalError(e);
    }
  }

  /**
   * Sets all of the attributes of this <code>JobAttributes</code> to
   * the same values as the attributes of obj.
   *
   * @param obj the <code>JobAttributes</code> to copy
   */
  public void set(JobAttributes obj) {
    copies = obj.copies;
    defaultSelection = obj.defaultSelection;
    destination = obj.destination;
    dialog = obj.dialog;
    fileName = obj.fileName;
    fromPage = obj.fromPage;
    maxPage = obj.maxPage;
    minPage = obj.minPage;
    multipleDocumentHandling = obj.multipleDocumentHandling;
    // okay because we never modify the contents of pageRanges
    pageRanges = obj.pageRanges;
    prFirst = obj.prFirst;
    prLast = obj.prLast;
    printer = obj.printer;
    sides = obj.sides;
    toPage = obj.toPage;
  }

  /**
   * Returns the number of copies the application should render for jobs
   * using these attributes. This attribute is updated to the value chosen
   * by the user.
   *
   * @return an integer greater than 0.
   */
  public int getCopies() {
    return copies;
  }

  /**
   * Specifies the number of copies the application should render for jobs
   * using these attributes. Not specifying this attribute is equivalent to
   * specifying <code>1</code>.
   *
   * @param copies an integer greater than 0
   * @throws IllegalArgumentException if <code>copies</code> is less than or equal to 0
   */
  public void setCopies(int copies) {
    if (copies <= 0) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "copies");
    }
    this.copies = copies;
  }

  /**
   * Sets the number of copies the application should render for jobs using
   * these attributes to the default. The default number of copies is 1.
   */
  public void setCopiesToDefault() {
    setCopies(1);
  }

  /**
   * Specifies whether, for jobs using these attributes, the application
   * should print all pages, the range specified by the return value of
   * <code>getPageRanges</code>, or the current selection. This attribute
   * is updated to the value chosen by the user.
   *
   * @return DefaultSelectionType.ALL, DefaultSelectionType.RANGE, or DefaultSelectionType.SELECTION
   */
  public DefaultSelectionType getDefaultSelection() {
    return defaultSelection;
  }

  /**
   * Specifies whether, for jobs using these attributes, the application
   * should print all pages, the range specified by the return value of
   * <code>getPageRanges</code>, or the current selection. Not specifying
   * this attribute is equivalent to specifying DefaultSelectionType.ALL.
   *
   * @param defaultSelection DefaultSelectionType.ALL, DefaultSelectionType.RANGE, or
   * DefaultSelectionType.SELECTION.
   * @throws IllegalArgumentException if defaultSelection is <code>null</code>
   */
  public void setDefaultSelection(DefaultSelectionType defaultSelection) {
    if (defaultSelection == null) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "defaultSelection");
    }
    this.defaultSelection = defaultSelection;
  }

  /**
   * Specifies whether output will be to a printer or a file for jobs using
   * these attributes. This attribute is updated to the value chosen by the
   * user.
   *
   * @return DesintationType.FILE or DesintationType.PRINTER
   */
  public DestinationType getDestination() {
    return destination;
  }

  /**
   * Specifies whether output will be to a printer or a file for jobs using
   * these attributes. Not specifying this attribute is equivalent to
   * specifying DesintationType.PRINTER.
   *
   * @param destination DesintationType.FILE or DesintationType.PRINTER.
   * @throws IllegalArgumentException if destination is null.
   */
  public void setDestination(DestinationType destination) {
    if (destination == null) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "destination");
    }
    this.destination = destination;
  }

  /**
   * Returns whether, for jobs using these attributes, the user should see
   * a print dialog in which to modify the print settings, and which type of
   * print dialog should be displayed. DialogType.COMMON denotes a cross-
   * platform, pure Java print dialog. DialogType.NATIVE denotes the
   * platform's native print dialog. If a platform does not support a native
   * print dialog, the pure Java print dialog is displayed instead.
   * DialogType.NONE specifies no print dialog (i.e., background printing).
   * This attribute cannot be modified by, and is not subject to any
   * limitations of, the implementation or the target printer.
   *
   * @return <code>DialogType.COMMON</code>, <code>DialogType.NATIVE</code>, or
   * <code>DialogType.NONE</code>
   */
  public DialogType getDialog() {
    return dialog;
  }

  /**
   * Specifies whether, for jobs using these attributes, the user should see
   * a print dialog in which to modify the print settings, and which type of
   * print dialog should be displayed. DialogType.COMMON denotes a cross-
   * platform, pure Java print dialog. DialogType.NATIVE denotes the
   * platform's native print dialog. If a platform does not support a native
   * print dialog, the pure Java print dialog is displayed instead.
   * DialogType.NONE specifies no print dialog (i.e., background printing).
   * Not specifying this attribute is equivalent to specifying
   * DialogType.NATIVE.
   *
   * @param dialog DialogType.COMMON, DialogType.NATIVE, or DialogType.NONE.
   * @throws IllegalArgumentException if dialog is null.
   */
  public void setDialog(DialogType dialog) {
    if (dialog == null) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "dialog");
    }
    this.dialog = dialog;
  }

  /**
   * Specifies the file name for the output file for jobs using these
   * attributes. This attribute is updated to the value chosen by the user.
   *
   * @return the possibly <code>null</code> file name
   */
  public String getFileName() {
    return fileName;
  }

  /**
   * Specifies the file name for the output file for jobs using these
   * attributes. Default is platform-dependent and implementation-defined.
   *
   * @param fileName the possibly null file name.
   */
  public void setFileName(String fileName) {
    this.fileName = fileName;
  }

  /**
   * Returns, for jobs using these attributes, the first page to be
   * printed, if a range of pages is to be printed. This attribute is
   * updated to the value chosen by the user. An application should ignore
   * this attribute on output, unless the return value of the <code>
   * getDefaultSelection</code> method is DefaultSelectionType.RANGE. An
   * application should honor the return value of <code>getPageRanges</code>
   * over the return value of this method, if possible.
   *
   * @return an integer greater than zero and less than or equal to <i>toPage</i> and greater than
   * or equal to <i>minPage</i> and less than or equal to <i>maxPage</i>.
   */
  public int getFromPage() {
    if (fromPage != 0) {
      return fromPage;
    } else if (toPage != 0) {
      return getMinPage();
    } else if (pageRanges != null) {
      return prFirst;
    } else {
      return getMinPage();
    }
  }

  /**
   * Specifies, for jobs using these attributes, the first page to be
   * printed, if a range of pages is to be printed. If this attribute is not
   * specified, then the values from the pageRanges attribute are used. If
   * pageRanges and either or both of fromPage and toPage are specified,
   * pageRanges takes precedence. Specifying none of pageRanges, fromPage,
   * or toPage is equivalent to calling
   * setPageRanges(new int[][] { new int[] { <i>minPage</i> } });
   *
   * @param fromPage an integer greater than zero and less than or equal to <i>toPage</i> and
   * greater than or equal to <i>minPage</i> and less than or equal to <i>maxPage</i>.
   * @throws IllegalArgumentException if one or more of the above conditions is violated.
   */
  public void setFromPage(int fromPage) {
    if (fromPage <= 0 ||
        (toPage != 0 && fromPage > toPage) ||
        fromPage < minPage ||
        fromPage > maxPage) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "fromPage");
    }
    this.fromPage = fromPage;
  }

  /**
   * Specifies the maximum value the user can specify as the last page to
   * be printed for jobs using these attributes. This attribute cannot be
   * modified by, and is not subject to any limitations of, the
   * implementation or the target printer.
   *
   * @return an integer greater than zero and greater than or equal to <i>minPage</i>.
   */
  public int getMaxPage() {
    return maxPage;
  }

  /**
   * Specifies the maximum value the user can specify as the last page to
   * be printed for jobs using these attributes. Not specifying this
   * attribute is equivalent to specifying <code>Integer.MAX_VALUE</code>.
   *
   * @param maxPage an integer greater than zero and greater than or equal to <i>minPage</i>
   * @throws IllegalArgumentException if one or more of the above conditions is violated
   */
  public void setMaxPage(int maxPage) {
    if (maxPage <= 0 || maxPage < minPage) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "maxPage");
    }
    this.maxPage = maxPage;
  }

  /**
   * Specifies the minimum value the user can specify as the first page to
   * be printed for jobs using these attributes. This attribute cannot be
   * modified by, and is not subject to any limitations of, the
   * implementation or the target printer.
   *
   * @return an integer greater than zero and less than or equal to <i>maxPage</i>.
   */
  public int getMinPage() {
    return minPage;
  }

  /**
   * Specifies the minimum value the user can specify as the first page to
   * be printed for jobs using these attributes. Not specifying this
   * attribute is equivalent to specifying <code>1</code>.
   *
   * @param minPage an integer greater than zero and less than or equal to <i>maxPage</i>.
   * @throws IllegalArgumentException if one or more of the above conditions is violated.
   */
  public void setMinPage(int minPage) {
    if (minPage <= 0 || minPage > maxPage) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "minPage");
    }
    this.minPage = minPage;
  }

  /**
   * Specifies the handling of multiple copies, including collation, for
   * jobs using these attributes. This attribute is updated to the value
   * chosen by the user.
   *
   * @return MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES or
   * MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES.
   */
  public MultipleDocumentHandlingType getMultipleDocumentHandling() {
    return multipleDocumentHandling;
  }

  /**
   * Specifies the handling of multiple copies, including collation, for
   * jobs using these attributes. Not specifying this attribute is equivalent
   * to specifying
   * MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES.
   *
   * @param multipleDocumentHandling MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES
   * or MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES.
   * @throws IllegalArgumentException if multipleDocumentHandling is null.
   */
  public void setMultipleDocumentHandling(MultipleDocumentHandlingType
      multipleDocumentHandling) {
    if (multipleDocumentHandling == null) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "multipleDocumentHandling");
    }
    this.multipleDocumentHandling = multipleDocumentHandling;
  }

  /**
   * Sets the handling of multiple copies, including collation, for jobs
   * using these attributes to the default. The default handling is
   * MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES.
   */
  public void setMultipleDocumentHandlingToDefault() {
    setMultipleDocumentHandling(
        MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES);
  }

  /**
   * Specifies, for jobs using these attributes, the ranges of pages to be
   * printed, if a range of pages is to be printed. All range numbers are
   * inclusive. This attribute is updated to the value chosen by the user.
   * An application should ignore this attribute on output, unless the
   * return value of the <code>getDefaultSelection</code> method is
   * DefaultSelectionType.RANGE.
   *
   * @return an array of integer arrays of 2 elements. An array is interpreted as a range spanning
   * all pages including and between the specified pages. Ranges must be in ascending order and must
   * not overlap. Specified page numbers cannot be less than <i>minPage</i> nor greater than
   * <i>maxPage</i>. For example: (new int[][] { new int[] { 1, 3 }, new int[] { 5, 5 }, new int[] {
   * 15, 19 } }), specifies pages 1, 2, 3, 5, 15, 16, 17, 18, and 19.
   */
  public int[][] getPageRanges() {
    if (pageRanges != null) {
      // Return a copy because otherwise client code could circumvent the
      // the checks made in setPageRanges by modifying the returned
      // array.
      int[][] copy = new int[pageRanges.length][2];
      for (int i = 0; i < pageRanges.length; i++) {
        copy[i][0] = pageRanges[i][0];
        copy[i][1] = pageRanges[i][1];
      }
      return copy;
    } else if (fromPage != 0 || toPage != 0) {
      int fromPage = getFromPage();
      int toPage = getToPage();
      return new int[][]{new int[]{fromPage, toPage}};
    } else {
      int minPage = getMinPage();
      return new int[][]{new int[]{minPage, minPage}};
    }
  }

  /**
   * Specifies, for jobs using these attributes, the ranges of pages to be
   * printed, if a range of pages is to be printed. All range numbers are
   * inclusive. If this attribute is not specified, then the values from the
   * fromPage and toPages attributes are used. If pageRanges and either or
   * both of fromPage and toPage are specified, pageRanges takes precedence.
   * Specifying none of pageRanges, fromPage, or toPage is equivalent to
   * calling setPageRanges(new int[][] { new int[] { <i>minPage</i>,
   * <i>minPage</i> } });
   *
   * @param pageRanges an array of integer arrays of 2 elements. An array is interpreted as a range
   * spanning all pages including and between the specified pages. Ranges must be in ascending order
   * and must not overlap. Specified page numbers cannot be less than <i>minPage</i> nor greater
   * than <i>maxPage</i>. For example: (new int[][] { new int[] { 1, 3 }, new int[] { 5, 5 }, new
   * int[] { 15, 19 } }), specifies pages 1, 2, 3, 5, 15, 16, 17, 18, and 19. Note that (new int[][]
   * { new int[] { 1, 1 }, new int[] { 1, 2 } }), is an invalid set of page ranges because the two
   * ranges overlap.
   * @throws IllegalArgumentException if one or more of the above conditions is violated.
   */
  public void setPageRanges(int[][] pageRanges) {
    String xcp = "Invalid value for attribute pageRanges";
    int first = 0;
    int last = 0;

    if (pageRanges == null) {
      throw new IllegalArgumentException(xcp);
    }

    for (int i = 0; i < pageRanges.length; i++) {
      if (pageRanges[i] == null ||
          pageRanges[i].length != 2 ||
          pageRanges[i][0] <= last ||
          pageRanges[i][1] < pageRanges[i][0]) {
        throw new IllegalArgumentException(xcp);
      }
      last = pageRanges[i][1];
      if (first == 0) {
        first = pageRanges[i][0];
      }
    }

    if (first < minPage || last > maxPage) {
      throw new IllegalArgumentException(xcp);
    }

    // Store a copy because otherwise client code could circumvent the
    // the checks made above by holding a reference to the array and
    // modifying it after calling setPageRanges.
    int[][] copy = new int[pageRanges.length][2];
    for (int i = 0; i < pageRanges.length; i++) {
      copy[i][0] = pageRanges[i][0];
      copy[i][1] = pageRanges[i][1];
    }
    this.pageRanges = copy;
    this.prFirst = first;
    this.prLast = last;
  }

  /**
   * Returns the destination printer for jobs using these attributes. This
   * attribute is updated to the value chosen by the user.
   *
   * @return the possibly null printer name.
   */
  public String getPrinter() {
    return printer;
  }

  /**
   * Specifies the destination printer for jobs using these attributes.
   * Default is platform-dependent and implementation-defined.
   *
   * @param printer the possibly null printer name.
   */
  public void setPrinter(String printer) {
    this.printer = printer;
  }

  /**
   * Returns how consecutive pages should be imposed upon the sides of the
   * print medium for jobs using these attributes. SidesType.ONE_SIDED
   * imposes each consecutive page upon the same side of consecutive media
   * sheets. This imposition is sometimes called <i>simplex</i>.
   * SidesType.TWO_SIDED_LONG_EDGE imposes each consecutive pair of pages
   * upon front and back sides of consecutive media sheets, such that the
   * orientation of each pair of pages on the medium would be correct for
   * the reader as if for binding on the long edge. This imposition is
   * sometimes called <i>duplex</i>. SidesType.TWO_SIDED_SHORT_EDGE imposes
   * each consecutive pair of pages upon front and back sides of consecutive
   * media sheets, such that the orientation of each pair of pages on the
   * medium would be correct for the reader as if for binding on the short
   * edge. This imposition is sometimes called <i>tumble</i>. This attribute
   * is updated to the value chosen by the user.
   *
   * @return SidesType.ONE_SIDED, SidesType.TWO_SIDED_LONG_EDGE, or SidesType.TWO_SIDED_SHORT_EDGE.
   */
  public SidesType getSides() {
    return sides;
  }

  /**
   * Specifies how consecutive pages should be imposed upon the sides of the
   * print medium for jobs using these attributes. SidesType.ONE_SIDED
   * imposes each consecutive page upon the same side of consecutive media
   * sheets. This imposition is sometimes called <i>simplex</i>.
   * SidesType.TWO_SIDED_LONG_EDGE imposes each consecutive pair of pages
   * upon front and back sides of consecutive media sheets, such that the
   * orientation of each pair of pages on the medium would be correct for
   * the reader as if for binding on the long edge. This imposition is
   * sometimes called <i>duplex</i>. SidesType.TWO_SIDED_SHORT_EDGE imposes
   * each consecutive pair of pages upon front and back sides of consecutive
   * media sheets, such that the orientation of each pair of pages on the
   * medium would be correct for the reader as if for binding on the short
   * edge. This imposition is sometimes called <i>tumble</i>. Not specifying
   * this attribute is equivalent to specifying SidesType.ONE_SIDED.
   *
   * @param sides SidesType.ONE_SIDED, SidesType.TWO_SIDED_LONG_EDGE, or
   * SidesType.TWO_SIDED_SHORT_EDGE.
   * @throws IllegalArgumentException if sides is null.
   */
  public void setSides(SidesType sides) {
    if (sides == null) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "sides");
    }
    this.sides = sides;
  }

  /**
   * Sets how consecutive pages should be imposed upon the sides of the
   * print medium for jobs using these attributes to the default. The
   * default imposition is SidesType.ONE_SIDED.
   */
  public void setSidesToDefault() {
    setSides(SidesType.ONE_SIDED);
  }

  /**
   * Returns, for jobs using these attributes, the last page (inclusive)
   * to be printed, if a range of pages is to be printed. This attribute is
   * updated to the value chosen by the user. An application should ignore
   * this attribute on output, unless the return value of the <code>
   * getDefaultSelection</code> method is DefaultSelectionType.RANGE. An
   * application should honor the return value of <code>getPageRanges</code>
   * over the return value of this method, if possible.
   *
   * @return an integer greater than zero and greater than or equal to <i>toPage</i> and greater
   * than or equal to <i>minPage</i> and less than or equal to <i>maxPage</i>.
   */
  public int getToPage() {
    if (toPage != 0) {
      return toPage;
    } else if (fromPage != 0) {
      return fromPage;
    } else if (pageRanges != null) {
      return prLast;
    } else {
      return getMinPage();
    }
  }

  /**
   * Specifies, for jobs using these attributes, the last page (inclusive)
   * to be printed, if a range of pages is to be printed.
   * If this attribute is not specified, then the values from the pageRanges
   * attribute are used. If pageRanges and either or both of fromPage and
   * toPage are specified, pageRanges takes precedence. Specifying none of
   * pageRanges, fromPage, or toPage is equivalent to calling
   * setPageRanges(new int[][] { new int[] { <i>minPage</i> } });
   *
   * @param toPage an integer greater than zero and greater than or equal to <i>fromPage</i> and
   * greater than or equal to <i>minPage</i> and less than or equal to <i>maxPage</i>.
   * @throws IllegalArgumentException if one or more of the above conditions is violated.
   */
  public void setToPage(int toPage) {
    if (toPage <= 0 ||
        (fromPage != 0 && toPage < fromPage) ||
        toPage < minPage ||
        toPage > maxPage) {
      throw new IllegalArgumentException("Invalid value for attribute " +
          "toPage");
    }
    this.toPage = toPage;
  }

  /**
   * Determines whether two JobAttributes are equal to each other.
   * <p>
   * Two JobAttributes are equal if and only if each of their attributes are
   * equal. Attributes of enumeration type are equal if and only if the
   * fields refer to the same unique enumeration object. A set of page
   * ranges is equal if and only if the sets are of equal length, each range
   * enumerates the same pages, and the ranges are in the same order.
   *
   * @param obj the object whose equality will be checked.
   * @return whether obj is equal to this JobAttribute according to the above criteria.
   */
  public boolean equals(Object obj) {
    if (!(obj instanceof JobAttributes)) {
      return false;
    }
    JobAttributes rhs = (JobAttributes) obj;

    if (fileName == null) {
      if (rhs.fileName != null) {
        return false;
      }
    } else {
      if (!fileName.equals(rhs.fileName)) {
        return false;
      }
    }

    if (pageRanges == null) {
      if (rhs.pageRanges != null) {
        return false;
      }
    } else {
      if (rhs.pageRanges == null ||
          pageRanges.length != rhs.pageRanges.length) {
        return false;
      }
      for (int i = 0; i < pageRanges.length; i++) {
        if (pageRanges[i][0] != rhs.pageRanges[i][0] ||
            pageRanges[i][1] != rhs.pageRanges[i][1]) {
          return false;
        }
      }
    }

    if (printer == null) {
      if (rhs.printer != null) {
        return false;
      }
    } else {
      if (!printer.equals(rhs.printer)) {
        return false;
      }
    }

    return (copies == rhs.copies &&
        defaultSelection == rhs.defaultSelection &&
        destination == rhs.destination &&
        dialog == rhs.dialog &&
        fromPage == rhs.fromPage &&
        maxPage == rhs.maxPage &&
        minPage == rhs.minPage &&
        multipleDocumentHandling == rhs.multipleDocumentHandling &&
        prFirst == rhs.prFirst &&
        prLast == rhs.prLast &&
        sides == rhs.sides &&
        toPage == rhs.toPage);
  }

  /**
   * Returns a hash code value for this JobAttributes.
   *
   * @return the hash code.
   */
  public int hashCode() {
    int rest = ((copies + fromPage + maxPage + minPage + prFirst + prLast +
        toPage) * 31) << 21;
    if (pageRanges != null) {
      int sum = 0;
      for (int i = 0; i < pageRanges.length; i++) {
        sum += pageRanges[i][0] + pageRanges[i][1];
      }
      rest ^= (sum * 31) << 11;
    }
    if (fileName != null) {
      rest ^= fileName.hashCode();
    }
    if (printer != null) {
      rest ^= printer.hashCode();
    }
    return (defaultSelection.hashCode() << 6 ^
        destination.hashCode() << 5 ^
        dialog.hashCode() << 3 ^
        multipleDocumentHandling.hashCode() << 2 ^
        sides.hashCode() ^
        rest);
  }

  /**
   * Returns a string representation of this JobAttributes.
   *
   * @return the string representation.
   */
  public String toString() {
    int[][] pageRanges = getPageRanges();
    String prStr = "[";
    boolean first = true;
    for (int i = 0; i < pageRanges.length; i++) {
      if (first) {
        first = false;
      } else {
        prStr += ",";
      }
      prStr += pageRanges[i][0] + ":" + pageRanges[i][1];
    }
    prStr += "]";

    return "copies=" + getCopies() + ",defaultSelection=" +
        getDefaultSelection() + ",destination=" + getDestination() +
        ",dialog=" + getDialog() + ",fileName=" + getFileName() +
        ",fromPage=" + getFromPage() + ",maxPage=" + getMaxPage() +
        ",minPage=" + getMinPage() + ",multiple-document-handling=" +
        getMultipleDocumentHandling() + ",page-ranges=" + prStr +
        ",printer=" + getPrinter() + ",sides=" + getSides() + ",toPage=" +
        getToPage();
  }
}
